package obecto.task.timeline
{
	import flash.display.FrameLabel;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import obecto.task.BaseInterruptibleTask;
	import obecto.utility.Listener;
	
	public class TimelineAnimation extends BaseInterruptibleTask
	{
		/**
		 * The target is a MovieClip with a timeline:
		 */ 
		public function set target(value : MovieClip) : void
		{
			if (value != null && value != _target)
			{
				_target = value;
				createLabelsIndex();
			}
		}
		public function get target() : MovieClip
		{
			return _target;
		}
		
		/**
		 * Identifier of the starting frame of the timeline animation.
		 * Can be a String label, as well as a Number.
		 */ 
		public var fromFrame : Object; 
			
		/**
		 * Identifier of the final frame of the timeline animation.
		 * Can be a String label, as well as a Number.
		 */
		public var toFrame : Object;
		
		private var _target : MovieClip;
		private var fromFrameNumber : Number = 0;
		private var toFrameNumber : Number = 0;
		private var labelsIndex : Dictionary;
		private var enterFrameListener : Listener;
		
		override public function end() : void
		{
			fromFrameNumber = fromFrame != null ? frameIndex(fromFrame) : target.currentFrame;
			toFrameNumber = frameIndex(toFrame);
			
			target.gotoAndStop(toFrameNumber);
			
			super.end();
		}
		
		override public function rollback() : void
		{
			fromFrameNumber = fromFrame != null ? frameIndex(fromFrame) : target.currentFrame;
			toFrameNumber = frameIndex(toFrame);
			
			target.gotoAndStop(fromFrameNumber);
			
			super.rollback();
		}
		
		override public function interrupt() : void
		{
			stopEnterFrameListener();
			
			super.interrupt();
		}
		
		override protected function startActual() : void
		{
			if (fromFrameNumber == 0 && toFrameNumber == 0)
			{
				fromFrameNumber = fromFrame != null ? frameIndex(fromFrame) : target.currentFrame;
				toFrameNumber = frameIndex(toFrame);
			
				target.gotoAndStop(fromFrameNumber);
			}
			
			startEnterFrameListener();
		}
		
		override protected function reverseActual() : void
		{
			if (fromFrameNumber == 0 && toFrameNumber == 0)
			{
				fromFrameNumber = fromFrame != null ? frameIndex(fromFrame) : target.currentFrame;
				toFrameNumber = frameIndex(toFrame);
			}
			
			// swap to and from frame numbers of not reversing already:
			if (frameIndex(fromFrame) == fromFrameNumber && frameIndex(toFrame) == toFrameNumber)
			{
				var previousFromFrameNumber : Number = fromFrameNumber;
				fromFrameNumber = toFrameNumber;
				toFrameNumber = previousFromFrameNumber;
			}
			
			// if still not running, ensure the correct position of the playhead:
			if (!running)
			{
				target.gotoAndStop(fromFrameNumber);
			}
			
			startEnterFrameListener();
		}
		
		override protected function forwardActual() : void
		{
			fromFrameNumber = fromFrame != null ? frameIndex(fromFrame) : target.currentFrame;
			toFrameNumber = frameIndex(toFrame);
			
			startEnterFrameListener();
		}
		
		override protected function complete() : void
		{
			stopEnterFrameListener();
				
			fromFrameNumber = toFrameNumber = 0;
			
			super.complete();
		}
		
		override protected function completeReverse() : void
		{
			stopEnterFrameListener();
				
			fromFrameNumber = toFrameNumber = 0;
			
			super.completeReverse();
		}
		
		/**
		 * Determines whether the requested timeline animation is going in a forward direction.
		 */ 
		private function get isForward() : Boolean
		{
			return fromFrameNumber < toFrameNumber;
		}
		
		/**
		 * Returns the index of the requested frame.
		 * 
		 * @param	frame	a String frame label, or a Number frame number
		 * @return 			the index of the frame between [1; target.totalFrames]
		 */ 
		private function frameIndex(frame : Object) : Number
		{
			if (isFrameNumber(frame))
				return Number(frame);
			
			if (labelsIndex[frame] != null)
				return Number(labelsIndex[frame]);
			
			//throw new Error("There is no such frame: " + frame);
			
			return NaN;
		}
		
		private function isFrameNumber(frame : Object) : Boolean
		{
			return frame.toString().search(/^[1-9][0-9]+$/) != -1;
		}
		
		private function createLabelsIndex() : void 
		{
			// store the playhead position of the target MovieClip:
			var storedCurrentFrame : Number = target.currentFrame;
			
			labelsIndex = new Dictionary();
			for each (var frameLabel : FrameLabel in target.currentLabels)
			{
				target.gotoAndStop(frameLabel.frame);
				labelsIndex[frameLabel.name] = target.currentFrame;
			}
			
			// restore the playhead position of the target MovieClip:
			target.gotoAndStop(storedCurrentFrame);
		}
		
		private function startEnterFrameListener() : void 
		{
			if (enterFrameListener == null)
			{
				enterFrameListener = Listener.create(target, Event.ENTER_FRAME, handleEnterFrame, null, false);
			}
			else if (!enterFrameListener.listening)
			{
				enterFrameListener.start();
			}
			
		}
		
		private function stopEnterFrameListener() : void 
		{
			if (enterFrameListener != null && enterFrameListener.listening)
			{
				enterFrameListener.stop();
			}
		}
		
		private function handleEnterFrame(e : Event) : void
		{
			if (target.currentFrame == toFrameNumber)
			{
				if (reversing)
				{
					completeReverse();
				}
				else
				{
					complete();
				}
			}
			else
			{
				if (isForward)
				{
					target.nextFrame();
				}
				else
				{
					target.prevFrame();
				}
			}
		}
		
	}
}